package de.fhaugsburg.games.boardgames.scrabble.consoleui;


import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;

import de.fhaugsburg.games.boardgames.Player.PlayerType;
import de.fhaugsburg.games.boardgames.commandline.CommandException;
import de.fhaugsburg.games.boardgames.commandline.CommandScanner;
import de.fhaugsburg.games.boardgames.commandline.ICommand;
import de.fhaugsburg.games.boardgames.exceptions.GameException;
import de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine;
import de.fhaugsburg.games.boardgames.scrabble.ScrabblePlayer;
import de.fhaugsburg.games.boardgames.scrabble.IScrabbleEngine.State;
import de.fhaugsburg.games.boardgames.scrabble.scrabbleboard.LetterPiece;
import de.fhaugsburg.games.boardgames.scrabble.terminal.TerminalServer;



public class CommandProcessor  implements IGuiMessage{

	
	
	private static enum Command implements ICommand {  
			NEWGAME ("newgame",  " * start new game with previously added players"),
			ADDPLAYER("addplayer",  "<playername>  * adds a new player to the game", String.class),
			ADDCPU	("addcpu",  "<playername>  * adds a new CPU-player to the game", String.class),
			ADDTERMINAL	("addterminal",  " * adds a free terminal slot for remote player"),
			ADD     ("add"    ,  "  <piece> <row> <column>  * an integer in range", Character.class, Character.class, Integer.class),
			REMOVE  ("remove",   "  <row> <column>  * an integer in range", Character.class, Integer.class),
			REMOVEALL  ("removeall",   "  removes all pieces put in current move"),
			AGREE	("agree" ,	 "  * accept words of the opponent"),
			REJECT	("reject" ,	 "  * reject words of the opponent"),
			COMMIT  ("commit",   "  * commits last move"),
			PASS	("pass",     "  * pass current move"),
			RENDER  ("render" ,  "  * render board"), 
			HELP    ("help"   ,  "  * list all commands"), 
			EXIT    ("exit"   ,  "  * exit program");

		   private String     token; 
		   private String     helpText; 
		   private Class<?>[] paramTypes;
		   private Object[]   params;

		   private Command(String token, String helpText, Class<?>... paramTypes) {
			   this.token = token;
			   this.helpText = helpText;
			   this.paramTypes = paramTypes;
		   }
		   
		   public String getToken(){
			   return this.token;
		   }
		   
		   public String getHelpText(){
			   return this.helpText;
		   }
		   
		   public Class<?>[] getParamTypes(){
			   return this.paramTypes;
		   }
		   
		   public Object[] getParams(){
			   return this.params;
		   }

		   public void setParams(Object[] params){
			   this.params = params;
		   }
	}
	
	
	
	private IScrabbleEngine engine;
	private BoardView boardView;
	private TerminalServer terminalServer;
	private boolean multiplayerGame = false;
	//braodcasted to all players
	private StringWriter publicOut = new StringWriter();
	private PrintWriter publicPrint = new PrintWriter(publicOut);
	//only for the eyes of current player
	private StringWriter privateOut = new StringWriter();
	private PrintWriter privatePrint = new PrintWriter(privateOut);
	
	
	
	
	
	public CommandProcessor(IScrabbleEngine gameEngine, BoardView gameView, TerminalServer terminalServer) {
		this.engine = gameEngine;
		this.boardView = gameView;
		this.terminalServer = terminalServer;
	}
	
	
	
	
	/**
	 * check if terminals answered
	 * @return
	 */
	private boolean isTerminalWaiting(){
		return  this.multiplayerGame;
	}

	
	/**
	 * connects all terminals added by "addterminal" command, waits, until terminal responds
	 */
	private void connectTerminals(){

		List<Integer> answeringTerminals = terminalServer.getAnsweringTerminals();

		if (answeringTerminals.isEmpty()){
			userError("No terminals found. Players must launch terminals before a new game can be started.");
		}else{
			//TODO: get player names from all answering terminals (should be threaded!)
			for (Integer terminalId : answeringTerminals){
				try {
					String terminalPlayerName = terminalServer.getInputLine(terminalId, "Please enter your name: ");
					int playerId = engine.addPlayer(terminalPlayerName, PlayerType.TERMINAL);
					//associate terminal with player
					terminalServer.setPlayersTerminal(playerId, terminalId);
					userMessage("Terminal-Player " + terminalPlayerName + " joined the game.");
				}catch (GameException e) {
					userError(e.getMessage());
				} catch (InterruptedException e1) {
					userError("Terminal " + terminalId + " quit.");
				}
			}
		}
	}
	
	
	
	
	
	public void process(){
		getUserInput();
	}
	
	
	/**
	 * the commandline and its processing, this is where currently all the interaction is done
	 */
	public void getUserInput(){
		CommandScanner scanner = new CommandScanner(Command.values()); 
		Command command;
		BufferedReader in = new BufferedReader(new InputStreamReader( System.in ) );
		
			
		while (true) { 
//			System.out.print("$>");
//			privatePrint.println("$>");
			
			String userInputString = "";
			String commandPrompt = "$>";
		
			
			try {
				/*
				 * FIXME: ugly userinput!
				 */
				if (engine.getGameState() != IScrabbleEngine.State.INITIAL && engine.getActivePlayer().getPlayerType() == PlayerType.TERMINAL){
					try {
						userInputString = terminalServer.getInputLine(terminalServer.getPlayersTerminal(engine.getActivePlayer().getId()), commandPrompt);
					} catch (InterruptedException e) {
						userError("problem reading, terminal closed?");
					}
					scanner.setInputstream(new StringReader(userInputString));
				}else{
//					privatePrint.println(commandPrompt);
					System.out.print("$>");
					scanner.setInputstream(new InputStreamReader( System.in ) );
				}
				
				
				
				command = (Command)scanner.next();
				Object[] params = command.getParams();
				
			   
			   switch (command) {
			   /**
			    * start an new game
			    */
			   case NEWGAME:
				   
				   //FIXME: DEBUG dummycode
//				   initDummyGame(engine);
//				   if (true) break;
				   
				   if (isTerminalWaiting()){
					   userMessage("Connecting terminals...");
					   connectTerminals();
				   }
				   userMessage("Starting new game.");
				   engine.newGame();
						
					try {
						engine.startGame();						
						
					} catch (GameException e) {
						userError(e.getMessage());
					}
					
			      break;
			      
			      
			   /**
			    * adds a (human) player to the game, required to start game
			    */
			   case ADDPLAYER:
				   if (engine.getNumberOfPlayers() < IScrabbleEngine.MAX_PLAYERS){
					    String playerName = (String) params[0];
						try {
							engine.addPlayer(playerName, PlayerType.LOCAL);
							System.out.println("Player " + playerName + " added.");
						}catch (GameException e) {
							userError(e.getMessage());
						}
				   }else{
					   userError("Too many players. Scrabble can be played by " + IScrabbleEngine.MAX_PLAYERS);
				   }
					break;

					
					
			   /**
			    * adds a (human) player to the game, required to start game
			    */
			   case ADDCPU:
				    if (engine.getNumberOfPlayers() < IScrabbleEngine.MAX_PLAYERS){
					    String cpuPlayerName = (String) params[0];
						try {
							engine.addPlayer(cpuPlayerName, PlayerType.CPU);
							System.out.println("CPU-Player " + cpuPlayerName + " added.");
						}catch (GameException e) {
							userError(e.getMessage());
						}
				    }else{
						userError("Too many players. Scrabble can be played by " + IScrabbleEngine.MAX_PLAYERS);
				    }
					break;
			      
					
					
			   /**
			    * adds a (human) player to the game, required to start game
			    */
			   case ADDTERMINAL:
				   if (engine.getNumberOfPlayers() < IScrabbleEngine.MAX_PLAYERS){
					   multiplayerGame  = true;		//indicates multiplayer game
					   //just add the slot 
					   terminalServer.addNewTerminalSlot();
					   userMessage("Remote player should now lauch the terminal application. Game starts, with 'newgame'.");
				   }else{
					   userError("Too many players. Scrabble can be played by " + IScrabbleEngine.MAX_PLAYERS);
				   }
					break;
			      
					
			      
			   /**
			    * current player adds a piece at given location
			    */
			   case ADD:{
				   char letterParam = (Character) params[0];
				   char rowParam = (Character) params[1];
				   int columnParam = (Integer) params[2];

				   if (engine.getGameState() == IScrabbleEngine.State.DROPPING){
						try {
							LetterPiece letter = LetterPiece.valueOf(String.valueOf(letterParam).toUpperCase());
							int row = Character.toUpperCase(rowParam) - 'A';
							int column = columnParam - 1;
							engine.setLetter(letter, row, column);
							repaint();
						}catch(Exception e){
							playerError(e.getMessage());
						}
					}else{
						userError("Before entering moves, please start a new game.");
						printHelp();
					}
			      break;
			   }
			   
			   /**
			    * remove recently placed letter at given position
			    */
			   case REMOVE: {
				   char rowParam = (Character) params[0];
				   int columnParam = (Integer) params[1];

				   if (engine.getGameState() == IScrabbleEngine.State.DROPPING){
						try {
							int row = Character.toUpperCase(rowParam) - 'A';
							int column = columnParam - 1;
							engine.removeLetter(row, column);
							repaint();
						}catch(Exception e){
							playerError(e.getMessage());
						}
					}else{
						userError("Before entering moves, please start a new game.");
						printHelp();
					}
			      break;    
			   }
			      
			   
			   /**
			    * remove all pieces from current move
			    */
			   case REMOVEALL:
				   engine.removeAllLetters();
				   break;
			   
				   
				   
			   /**
			    * commit the move   
			    */
			   case COMMIT:
				   if (engine.getGameState() == IScrabbleEngine.State.DROPPING){
					   try {
						   engine.commitMove();
						   repaint();
					   } catch (GameException e) {
						   playerError(e.getMessage());
					   }
				   }else{
					   if (engine.getGameState() == IScrabbleEngine.State.DROPPING){
						   playerError("please agree or reject");
					   }else{
						   playerError("before committing, you first have to start a game and put some pieces. type 'help' for more.");
					   }
				   }
				   break;

			   /**
			    * accept commit from other player
			    */
			   case AGREE:
				   if (engine.getGameState() == State.COMMITED){
					   engine.commitAggreed();
					   repaint();
				   }else{
					   playerError("wait for other player to commit, before you accept the move");
				   }
				   break;

			   /**
			    * reject the other players commit
			    */
			   case REJECT:
				   if (engine.getGameState() == State.COMMITED){
					   engine.commitRejected();
					   repaint();
				   }else{
					   playerError("wait for other player to commit, before you reject the move");
				   }
				   break;

			   /**
			    * do not make a move
			    */
			   case PASS:
				   if (engine.getGameState() == State.DROPPING){
					   engine.passMove();
				   }else{
					   playerError("can not pass now");					   
				   }
				   break;
				   
				   
			   /**
			    * just prints the board
			    */
			   case RENDER:
				   //TODO: should only render private terminal view for the current player
				   repaint();
				   break;
				   
				   
			   /**
			    * prints command help
			    */
			   case HELP:
					printHelp();
					break;
				
			   /**
			    * exit game
			    */
			   case EXIT:
					System.exit(0);
					break;
				
				/**
				 * unknown command
				 */
				default:
					userError("Unknown command.");
					printHelp();
					break;
			   }
			} catch (CommandException e) {
				userError("Sorry: " + e.getMessage());
			} 

		}
	}

	
	/**
	 * prints available commands
	 */
	public void printHelp(){
		System.out.println("Commands available:");
		for (Command command : Command.values()){
			System.out.println(command.getToken() + "\t" + command.getHelpText());
		}
	}

	
	
	/**
	 * repaints the current state of the game
	 */
	public void repaint() {
		
		ScrabblePlayer activePlayer = engine.getActivePlayer();
		
		//render locally 
	//	boardView.render();
		//render on all terminals
		//if (isTerminalWaiting()){
			//terminalServer.sendMessageToAll(engine.getBoard().toString());
		//}
		publicPrint.println(engine.getBoard().toString());
		
		
		//prints, whatever engine has to say, normally no user interaction is needed
		if(engine.gameMessageAvailable()){
			String gameMessage = engine.getGameMessage();
			//System.out.println(gameMessage);
			//System.out.println();
			publicPrint.println(gameMessage);
		}
		
		//user interaction based on gamestate
		switch (engine.getGameState()){
		case INITIAL:
				break;
		case COMMITED:
			publicPrint.println("word(s) to <agree> or <reject>: " + engine.getLastProdudedWordsAsString());
			publicPrint.println();
		case DROPPING:
			publicPrint.println("Current Player: " + activePlayer.getPlayerName() + " (score: " + activePlayer.getCredits() + ")");
			privatePrint.println("My pieces: " + activePlayer.getMyPiecesString());
			break;
		case GAMEOVER:
			publicPrint.println("Gameover: " + activePlayer.getPlayerName() + " won the game.");
			break;
		}
		updateView();
	}
   
	
	
	
	/**
	 * updates all console views
	 */
	private void updateView(){
		ScrabblePlayer activePlayer = engine.getActivePlayer();
		
		//update boardview on all terminals
		if (isTerminalWaiting()){
			terminalServer.sendMessageToAll(publicOut.toString());
		}
		//local update of boardview
		System.out.println(publicOut.toString());
		
		//distribute the private message for the players
		switch (activePlayer.getPlayerType()){
		case TERMINAL:
			int playersTerminal = terminalServer.getPlayersTerminal(activePlayer.getId());
			terminalServer.sendMessage(privateOut.toString(), playersTerminal);
			break;
		case LOCAL:
			System.out.println(privateOut.toString());
			break;
		case CPU:
			privateOut.getBuffer().setLength(0);
			break;
		}
		//flush the buffers
		publicOut.getBuffer().setLength(0);
		privateOut.getBuffer().setLength(0);
	}
	
	
	
	
	/**
	 * just a dummy to test-run game
	 * @param engine
	 */
	private void initDummyGame(IScrabbleEngine engine){
		
		//DEBUG
		try {
			engine.addPlayer("Alice", PlayerType.LOCAL);
			engine.addPlayer("Bob", PlayerType.LOCAL); 
			
			engine.newGame();
			engine.startGame();
			
			engine.setLetter(engine.getActivePlayer().getMyPieces().get(0), 7, 7);
			engine.setLetter(engine.getActivePlayer().getMyPieces().get(0), 7, 8);
			engine.setLetter(engine.getActivePlayer().getMyPieces().get(0), 7, 9);
			engine.setLetter(engine.getActivePlayer().getMyPieces().get(0), 7, 10);
			engine.setLetter(engine.getActivePlayer().getMyPieces().get(0), 7, 11);
			
			engine.commitMove();
			engine.commitAggreed();
			
		} catch (GameException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
		
		
		
	}
	
	
	/**
	 * displays userError
	 * @param message
	 */
	private void userError(String message){
		System.err.println(message);
	}
	
	private void userMessage(String message){
		System.out.println(message);
	}
	
	
	/**
	 * error-messages only visible to current player
	 * @param mesaage
	 */
	private void playerError(String message){
		privatePrint.println(message);		
	}
	
	/**
	 * messages only visible for current player
	 * @param message
	 */
	private void playerMessage(String message){
		privatePrint.println(message);
	}
	
	
	/**
	 * called from engine, when done
	 */
	public void idle() {
		//getUserInput();		// results in endless recursion!
	}
	
	
}
